/*
 *  libKara - Library for basic instant messaging functions
 *  Copyright (C) 2007 Markus Karnik
 *
 *  This program is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, 
 *  or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along 
 *  with this program; if not, see <http://www.gnu.org/licenses/>.
 *  
 */

package de.karnik.libkara.proto.xmpp;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Vector;

import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import de.karnik.helper.StringFunctions;
import de.karnik.helper.LogFunctions;
import de.karnik.libkara.helper.SaslHelper;
import de.karnik.libkara.proto.xmpp.packages.SimpleXmlPackage;
import de.karnik.libkara.proto.xmpp.packages.XmlPackage;
import de.karnik.libkara.proto.xmpp.packages.XmlPackageStorage;
import de.karnik.libkara.proto.xmpp.packages.XmppBaseStanza;
import de.karnik.libkara.proto.xmpp.packages.XmppIQChildBind;
import de.karnik.libkara.proto.xmpp.packages.XmppIQChildRoster;
import de.karnik.libkara.proto.xmpp.packages.XmppIQChildSession;
import de.karnik.libkara.proto.xmpp.packages.XmppIQStanza;
import de.karnik.libkara.proto.xmpp.packages.XmppMessageStanza;
import de.karnik.libkara.proto.xmpp.packages.XmppPresenceStanza;
import de.karnik.libkara.proto.xmpp.packages.XmppRosterItem;
import de.karnik.libkara.proto.xmpp.packages.XmppStreamErrorChild;
import de.karnik.libkara.proto.xmpp.packages.XmppStreamErrorPackage;


/**
 * Parses the XMPP input stream to handle incoming messages, presences or iqs.
 * 
 * @author Markus Karnik
 * @version 1.0
 * @since 1.0
 */
public class XmppParser extends Thread {

	/**
	 * Holds the XML pull parser.
	 */
	private XmlPullParser parser = null;
	/**
	 * The buffered input stream.
	 */
	private BufferedReader xmppReader;
	/**
	 * The XMPP settings.
	 */
	private XmppSettings xmppSettings = null;
	/**
	 * Hold the listener for possible parser callbacks.
	 */
	private XmppParserListener xmppParserListener;
	/**
	 * Hold listener for possible thread callbacks.
	 */
	private XmppThreadListener xmppThreadListener;
	/**
	 * SaslHelper for useful SASL functionality. (e.g. creating responses )
	 */
	private SaslHelper saslHelper = null;
	/**
	 * Package storage to store incoming and outgoing packages.
	 */
	private XmlPackageStorage packageStorage = null;
	/**
	 * The unique thread id.
	 */
	private String threadID = "standard";
	/**
	 * Indicates if thread is running.
	 */
	private boolean running;
	
	/**
	 * Basic constructor of the parser.
	 * 
	 * @param threadID  The unique thread id.
	 * @param xmppReader The XmppBufferedReader to read the XMPP input stream.
	 * @param xmppSettings The XmppSettings to store and read the stream and client configuration.
	 * @param packageStorage The XmppPackageStorage to store the parsed packages.
	 * @throws XmlPullParserException
	 */
	public XmppParser( String threadID, BufferedReader xmppReader, 
						XmppSettings xmppSettings, XmlPackageStorage packageStorage ) throws XmlPullParserException {
		
		this.threadID = threadID;
		this.xmppReader = xmppReader;
		
		createParser();
		
		this.xmppSettings = xmppSettings;
		this.packageStorage = packageStorage;
		saslHelper = new SaslHelper( xmppSettings );
	}
	
	/**
	 * Shuts down this Thread.
	 */
	public void shutdown() {
		
		// send unavailable package
		XmppPresenceStanza presence = new XmppPresenceStanza();
		presence.setType( XmppPresenceStanza.TYPE_UNAVAILABLE );
		presence.setChildStatus( "en", "Going offline!" );
		packageStorage.addOutputPackage( presence );
		
		// send closing stream tag to close the stream
		SimpleXmlPackage simplePackage = new SimpleXmlPackage( "stream:stream", null );
		simplePackage.setJustClosing( true );
		packageStorage.addOutputPackage( simplePackage );
		
		running = false;
		parser = null;
		
		if( xmppThreadListener != null )
			xmppThreadListener.threadStopped( threadID );
	}
	
	/**
	 * Overrides the XmppBufferedReader and resets the parser. 
	 * 
	 * @param xmppReader The XmppBufferedReader for the replacement.
	 * @throws XmlPullParserException
	 */
	public void resetParser( BufferedReader xmppReader ) throws XmlPullParserException {
		this.xmppReader = xmppReader;
		resetParser();
	}
	
	/**
	 * Creates a new XmlPullParser.
	 * 
	 * @throws XmlPullParserException
	 */
	public void createParser() throws XmlPullParserException {
		parser = new MXParser();
		
        parser.setFeature( XmlPullParser.FEATURE_PROCESS_NAMESPACES, true );
        parser.setInput( this.xmppReader );
	}
	
	/**
	 * Resets the current running parser.
	 * 
	 * @throws XmlPullParserException
	 */
	public void resetParser() throws XmlPullParserException {
		createParser();
	}
	
	/**
	 * Sets a xmppParserListener for possible parser callbacks.
	 * 
	 * @param xmppParserListener The listener to set.
	 */
	public void setXmppParserListener( XmppParserListener xmppParserListener ) {
		this.xmppParserListener = xmppParserListener;
	}

	/**
	 * Sets a xmppThreadListener for possible thread callbacks.
	 * 
	 * @param xmppThreadListener The listener to set.
	 */
	public void setXmppThreadListener( XmppThreadListener xmppThreadListener ) {
		this.xmppThreadListener = xmppThreadListener;
	}
	
	/**
	 * Clears the xmppThreadListener object.
	 */
	public void removeXmppThreadListener() {
		this.xmppThreadListener = null;
	}
	
	/**
	 * Parses stream errors.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseStreamError( XmppStreamErrorPackage error ) throws XmlPullParserException, IOException {
		boolean running = true;
		XmppStreamErrorChild child;
		
		while( running ) {
			int event = this.parser.next();

			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				if( this.parser.getNamespace().equals( XmlPackage.NS_XMPP_STREAMS ) && !this.parser.getName().equals( "text" ) ) {
					error.setCondition( this.parser.getName() );
				}
				
				if( this.parser.getName().equals( "text" ) ) {
					if( this.parser.getNamespace().equals( XmlPackage.NS_XMPP_STREAMS )  ) {
						
						System.out.println( this.parser.nextText() );
						System.out.println( this.parser.getAttributeValue( 0 ) );
						
						child = new XmppStreamErrorChild( this.parser.nextText(), this.parser.getAttributeValue( 0 ) );
						error.addStreamErrorChild( child );
					}
				}
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "error" ) )
            		running = false;
            }	
		}
	}
	
	/**
	 * Parses the authentication mechanisms to configure stream and client.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseAuthMechanisms() throws XmlPullParserException, IOException {
		boolean running = true;
		
		while( running ) {
			int event = this.parser.next();

			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				if( this.parser.getName().equals( "mechanism" ) )
					this.xmppSettings.addEnabledAuthMethods( this.parser.nextText() );
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "mechanisms" ) )
            		running = false;
            }	
		}
	}
	
	/**
	 * Parses the compression methods to configure stream and client.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseCompressionMethods() throws XmlPullParserException, IOException {
		boolean running = true;
		
		while( running ) {
			int event = this.parser.next();

			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				if( this.parser.getName().equals( "method" ) )
					this.xmppSettings.addEnabledCompressionMethods( this.parser.nextText() );
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "compression" ) )
            		running = false;
            }	
		}
	}
	
	/**
	 * Parses the stream features to configure stream and client.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseFeatures() throws XmlPullParserException, IOException {
		boolean running = true;
	
		while( running ) {
		
			int event = this.parser.next();

			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				// server is tls enabled
				if( this.parser.getName().equals( "starttls" ) ) {
					this.getXmppSettings().putBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_ENABLED, true );
	
				// parse server enabled compression methods
				} else if( this.parser.getName().equals( "compression" ) ){
					this.parseCompressionMethods();
					
				// parse server enabled sasl auth mechanisms
				} else if( this.parser.getName().equals( "mechanisms" ) ){
					this.parseAuthMechanisms();
				
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// server requires to bind resources
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++					
				} else if( this.parser.getName().equals( "bind" ) ){
					this.getXmppSettings().putBooleanData( XmppSettings.BOOLEAN_VALUE_BIND_REQUIRED, true );
					
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// server requires to create a session
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++					
				} else if( this.parser.getName().equals( "session" ) ){
					this.getXmppSettings().putBooleanData( XmppSettings.BOOLEAN_VALUE_SESSION_REQUIRED, true );
				}
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	
            	// features are finished
            	if( this.parser.getName().equals( "features" ) ) {
            		running = false;

            	// server requires tls
            	} else if( this.parser.getName().equals( "required" ) ) {
            		this.getXmppSettings().putBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_REQUIRED, true );
            	}
            }	
		}
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	public void run() {
		try {
			this.startParsing();
		} catch ( Exception e ) {
			LogFunctions.log( "XmppParser.run", "Exception", e.getMessage() );
			LogFunctions.log( "XmppParser.run", "Exception", e.getStackTrace() );
		}
	}
	
	/**
	 * Entrypoint. Start parsing the XML input stream.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void startParsing() throws XmlPullParserException, IOException, NullPointerException {
		
		SimpleXmlPackage simplePackage = null;
		
		this.running = true;
		int event;
		
		// send running callback
		if( this.xmppThreadListener != null )
			this.xmppThreadListener.threadStarted( this.threadID );
		
		event = parser.getEventType();
		
		while( this.running ) {
			
			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {	
				
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// start parsing the stream header
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				if( this.parser.getName().equals( "stream" ) ) {
	
					// read and process the stream header while namespace is jabber:client
					if( this.parser.getNamespace( null ).equals( "jabber:client" ) ) {
					
						// read and set attributes to the settings object
						int attributes = this.parser.getAttributeCount();
						for( int i = 0; i < attributes; i++ ) {
							// set connection id
							if( this.parser.getAttributeName( i ).equals( "id" ) )
								this.xmppSettings.putStringData( XmppSettings.STRING_VALUE_CONNECTIONID, 
																	this.parser.getAttributeValue( i ) );

							// set service id
							if( this.parser.getAttributeName( i ).equals( "from" ) )
								this.xmppSettings.putStringData( XmppSettings.STRING_VALUE_SERVICEID, 
																	this.parser.getAttributeValue( i ) );
							
							// set service lang
							if( this.parser.getAttributeName( i ).equals( "lang" ) )
								this.xmppSettings.putStringData( XmppSettings.STRING_VALUE_SERVICELANG, 
																	this.parser.getAttributeValue( i ) );
						}
					
					// close the stream on every other namespace than jabber:client (e.g. jabber:server)
					} else {
						running = false;
					}
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// start parsing possible stream errors
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "error" ) ){ 	
					XmppStreamErrorPackage error = new XmppStreamErrorPackage();
					this.parseStreamError( error );
					this.packageStorage.addInputPackage( error );
					
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// start parsing the server features
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "features" ) ){ 
					// parse server features
					this.parseFeatures();
					
					// start tls negotation if tls isnt established
					if( !this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_ESTABLISHED ) 
							&&  ( this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_REQUIRED ) 
									|| ( this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_ENABLED ) 
											&& this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_CLIENT_TLS_ENABLED ) 
										)  
								)  
					) {

						// send tls package to server to start tls negotation
						simplePackage = new SimpleXmlPackage( "starttls", SimpleXmlPackage.NS_XMPP_TLS );
						this.packageStorage.addOutputPackage( simplePackage );
					}
					
					// TODO PRÜFEN!!!
					// start authentication
					if( ( this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_ESTABLISHED ) 
							|| ( !this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_TLS_REQUIRED ) 
									&& !this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_CLIENT_TLS_ENABLED ) 
								) 
						) 
							&& !this.xmppSettings.getBooleanData( XmppSettings.BOOLEAN_VALUE_AUTHENTICATED )
					) {
						
						// check available auth methods an chose best possible one
						Vector<String> authMethods = xmppSettings.getEnabledAuthMethods();
						
						// use DIGEST-MD5 method
						if( authMethods.contains( "DIGEST-MD5" ) ) {
							
							simplePackage = new SimpleXmlPackage( "auth", SimpleXmlPackage.NS_XMPP_SASL );
							simplePackage.addAttribute( "mechanism", "DIGEST-MD5" );
								
							this.packageStorage.addOutputPackage( simplePackage );
								
						// use PLAIN method
						} else if( authMethods.contains( "PLAIN" ) ) {

							simplePackage = new SimpleXmlPackage( "auth", SimpleXmlPackage.NS_XMPP_SASL );
							simplePackage.addAttribute( "mechanism", "PLAIN" );
							
							this.packageStorage.addOutputPackage( simplePackage );
							
						// cannot auth on this server
						} else {
							this.shutdown();
						}

					}
					
					// send bind package if required
					if( this.getXmppSettings().getBooleanData( XmppSettings.BOOLEAN_VALUE_BIND_REQUIRED ) ) {
						XmppIQStanza iq = new XmppIQStanza();
						XmppIQChildBind bind = new XmppIQChildBind();
						
						iq.setType( XmppIQStanza.TYPE_SET );
						iq.setId( this.xmppSettings.getStringData( XmppSettings.STRING_VALUE_RESOURCEID ) );

						bind.addChildResources( "Home" );
						iq.setChild( bind );
						
						this.packageStorage.addOutputPackage( iq );
					}
					
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// server is ready for tls negotation
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "proceed" ) ){
						
					// start tls negotation
					if( this.xmppParserListener != null )
						this.xmppParserListener.tlsNegotationConfirmed();

				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// server challanges sasl auth
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "challenge" ) ) {
					
					// create and send sasl response
					simplePackage = new SimpleXmlPackage( "response", SimpleXmlPackage.NS_XMPP_SASL );
					simplePackage.setContent( 
							StringFunctions.encodeBase64( 
									this.saslHelper.createResponse( 
											StringFunctions.decodeBase64( this.parser.nextText() ) ) ) );
					
					this.packageStorage.addOutputPackage( simplePackage );
					
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// sasl auth was successful
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "success" ) ) {
					// reset parser and resend stream header
					this.resetParser();
					this.sendStandardHeader();

				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// parse incoming iq package
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "iq" ) ) {
					this.parseIQ();

				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// parse incoming message package
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "message" ) ) {
					this.parseMessage();

				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				// parse incoming presence package
				// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
				} else if( this.parser.getName().equals( "presence" ) ) {
					this.parsePresence();

				}
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
			} else if( event == XmlPullParser.END_TAG  ) {
				if( this.parser.getName().equals( "stream" ) )
						this.shutdown();
			}
			if( running )
				event = this.parser.next();
        }
	}

	/**
	 * Parses the base attributes of basic XMPP stanzas.
	 * 
	 * @param stanza The XmppBaseStanza to save the values in.
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseBaseAttributes( XmppBaseStanza stanza ) throws XmlPullParserException, IOException {
		
		// read and set attributes to the settings object
		int attributes = this.parser.getAttributeCount();

		for( int i = 0; i < attributes; i++ ) {
			// read id value
			if( this.parser.getAttributeName( i ).equals( "id" ) )
				stanza.setId( this.parser.getAttributeValue( i ) );

			// read from value			
			if( this.parser.getAttributeName( i ).equals( "from" ) )
				stanza.setFrom( this.parser.getAttributeValue( i ) );

			// read lang value			
			if( this.parser.getAttributeName( i ).equals( "lang" ) )
				stanza.setLang( this.parser.getAttributeValue( i ) );
			
			// read type value			
			if( this.parser.getAttributeName( i ).equals( "type" ) )
				stanza.setType( this.parser.getAttributeValue( i ) );
			
			// read to value			
			if( this.parser.getAttributeName( i ).equals( "to" ) )
				stanza.setTo( this.parser.getAttributeValue( i ) );
			
		}
		
		
	}
	
	/**
	 * Parses iq bind options and values.
	 * 
	 * @param bind The XmppIQChildBind object to store the values in.
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseIQBind( XmppIQChildBind bind ) throws XmlPullParserException, IOException {
		boolean running = true;
		
		while( running ) {
			int event = this.parser.next();

			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				// add items
				if( this.parser.getName().equals( "jid" ) ) {
					bind.addChildJid( this.parser.nextText() );
					
				// add resources
				} else if( this.parser.getName().equals( "resource" ) ) {
					bind.addChildResources( this.parser.nextText() );
				}
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "bind" ) )
            		running = false;
            }
		}
	}
	
	/**
	 * Parses iq roster options and values.
	 * 
	 * @param roster The XmppIQChildRoster object to store the values in.
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseIQRoster( XmppIQChildRoster roster ) throws XmlPullParserException, IOException {
		boolean running = true;
		XmppRosterItem item = null;
		
		LogFunctions.log( "XmppParser.parseIQRoster", "parseIQRoster" );
		
		while( running ) {
			int event = this.parser.next();
			
			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				// add item
				if( this.parser.getName().equals( "item" ) ) {
					item = new XmppRosterItem();
					
					// parse attributes
					for( int i = 0; i < this.parser.getAttributeCount(); i++ ) {

						// parse jid
						if( this.parser.getAttributeName( i ).equals( "jid" ) ) {
							item.setJid( this.parser.getAttributeValue( i ) );
							
						// parse name/alias
						} else if( this.parser.getAttributeName( i ).equals( "name" ) ) {
							item.setName( this.parser.getAttributeValue( i ) );

						// parse ask
						} else if( this.parser.getAttributeName( i ).equals( "ask" ) ) {
							item.setAsk( this.parser.getAttributeValue( i ) );
							
						// parse subscription
						} else if( this.parser.getAttributeName( i ).equals( "subscription" ) ) {
							item.setSubscription( this.parser.getAttributeValue( i ) );
						}
					}		

				} else if( this.parser.getName().equals( "group" ) ) {
					if( item != null )
						item.addChildGroup( this.parser.nextText() );
				}
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "query" ) )
            		running = false;
            	
            	if( this.parser.getName().equals( "item" ) ) {
					if( item != null )
						roster.addXmppRosterItem( item );
            	}
            }
		}
	}
	
	/**
	 * Parses iq packages. Parsed packages will be written to the input package storage.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseIQ() throws XmlPullParserException, IOException {
		XmppIQStanza iq = new XmppIQStanza();
		boolean running = true;
		
		while( running ) {
			int event = this.parser.getEventType();
			
			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				if( this.parser.getName().equals( "iq" ) ) {
					this.parseBaseAttributes( iq );
				} else {

					// parse a bind iq package
					if( this.parser.getName().equals( "bind" )  ) {
						XmppIQChildBind bind = new XmppIQChildBind();
						this.parseIQBind( bind );
						iq.setChild( bind );
					
					// parse a bind session package					
					} else if( this.parser.getName().equals( "session" ) ) {
						XmppIQChildSession session = new XmppIQChildSession();
						iq.setChild( session );
						
					// parse a bind roster package					
					} else if( this.parser.getName().equals( "query" ) ) {
						XmppIQChildRoster roster = new XmppIQChildRoster();
						this.parseIQRoster( roster );
						iq.setChild( roster );
					}
					
					if( iq.getType() != null && iq.getType().equals( XmppBaseStanza.TYPE_ERROR ) )
						if( this.parser.getName().equals( "error" ) ) 
							this.parseStanzaError( iq );
				}


			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if( event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "iq" ) )
            		running = false;
            }
			
			if( running )
				this.parser.next();
		}
		this.packageStorage.addInputPackage( iq );
	}	

	/**
	 * Parses error packages and adds error message to the given stanza.
	 * 
	 * @param stanza
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseStanzaError( XmppBaseStanza stanza ) throws XmlPullParserException, IOException {
		LogFunctions.log( "ERROR ------------------");
	}
	
	/**
	 * Parses message packages. Parsed packages will be written to the input package storage.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parseMessage() throws XmlPullParserException, IOException {
		XmppMessageStanza message = new XmppMessageStanza();
		boolean running = true;
		
		while( running ) {
			
			int event = this.parser.getEventType();
			
			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				if( this.parser.getName().equals( "message" ) ) {
					this.parseBaseAttributes( message );
				} else {
					// parse show child package
					if( this.parser.getName().equals( "body" )  ) {
						if( this.parser.getAttributeCount() == 1 ) {
							message.setChildBody( this.parser.getAttributeValue( 0 ), new StringBuffer( this.parser.getText() ) );
						} else {
							message.setDefaultChildBody( new StringBuffer( this.parser.nextText() ) );
						}
					}  else if( this.parser.getName().equals( "subject" ) ) {
						if( this.parser.getAttributeCount() == 1 ) {
							message.setChildSubject( this.parser.getAttributeValue( 0 ), this.parser.getText() );
						} else {
							message.setDefaultChildSubject( this.parser.nextText() );
						}
					} else if( this.parser.getName().equals( "thread" ) ) {
						message.setChildThread( this.parser.nextText() );		
					} else if( this.parser.getName().equals( "html" ) ) {
						this.parser.nextTag();	
					}
					
					// parse error
					if( message.getType() != null && message.getType().equals( XmppBaseStanza.TYPE_ERROR ) )
						if( this.parser.getName().equals( "error" ) ) 
							this.parseStanzaError( message );
					
				}
				
			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if(event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "message" ) )
            		running = false;
            }	
			if( running )
				this.parser.next();
		}

		this.packageStorage.addInputPackage( message );
		
	}
	
	/**
	 * Parses presence packages. Parsed packages will be written to the input package storage.
	 * 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private void parsePresence() throws XmlPullParserException, IOException {
		XmppPresenceStanza presence = new XmppPresenceStanza();
		boolean running = true;
		
		while( running ) {
			int event = this.parser.getEventType();
			
			// ##################################################################################################################
			// process the START-TAGS
			// ##################################################################################################################
			if( event == XmlPullParser.START_TAG ) {
				
				if( this.parser.getName().equals( "presence" ) ) {
					this.parseBaseAttributes( presence );
				} else {
					// parse show child package
					if( this.parser.getName().equals( "show" )  ) {
						presence.setChildShow( this.parser.nextText() );
					
					// parse bind session package					
					} else if( this.parser.getName().equals( "status" ) ) {
						if( this.parser.getAttributeCount() == 1 ) {
							presence.setChildStatus( this.parser.getAttributeValue( 0 ), this.parser.getText() );
						} else {
							presence.setDefaultChildStatus( this.parser.nextText() );
						}
						
					// parse priority child package					
					} else if( this.parser.getName().equals( "priority" ) ) {
						try {
							presence.setChildPriority( Integer.parseInt( this.parser.nextText() ) );
						} catch( NumberFormatException e ) {
							presence.setChildPriority( -1 );
						}
						
					}
					
					// parse error
					if( presence.getType() != null && presence.getType().equals( XmppBaseStanza.TYPE_ERROR ) )
						if( this.parser.getName().equals( "error" ) ) 
							this.parseStanzaError( presence );
					
				}


			// ##################################################################################################################
			// process the END-TAGS
			// ##################################################################################################################
            } else if( event == XmlPullParser.END_TAG) {
            	if( this.parser.getName().equals( "presence" ) )
            		running = false;
            }
			
			if( running )
				this.parser.next();
		}

		this.packageStorage.addInputPackage( presence );
	}
	
	/**
	 * Adds the standard stream header to the package storage.
	 */
	protected void sendStandardHeader() {
		SimpleXmlPackage header = new SimpleXmlPackage( "stream:stream", SimpleXmlPackage.NS_XMPP_CLIENT );
		header.setSendXMLHeader( true );
		header.setJustOpening( true );
		header.addAttribute( "to", this.xmppSettings.getStringData( XmppSettings.STRING_VALUE_SERVICEID ) );
		header.addAttribute( "xmlns:stream", SimpleXmlPackage.NS_XMPP_STREAM );
		header.addAttribute( "version", "1.0" );
		
		this.packageStorage.addOutputPackage( header );
	}

	/**
	 * Returns the current configuration.
	 * 
	 * @return The current XmppSettings object.
	 */
	public XmppSettings getXmppSettings() {
		return this.xmppSettings;
	}
}
